# -*- coding: utf-8 -*-
"""
Created on Fri May  6 20:39:08 2022

@author: xtp

"""
from scipy import stats
from matplotlib.lines import Line2D
from matplotlib.patches import Patch
import seaborn
import re
import itertools
import seaborn as sns
import numpy as np
import matplotlib.pyplot as plt
from statannotations.Annotator import Annotator
from sklearn.metrics import ConfusionMatrixDisplay

class Analysis():
    '''
    统计学分析，绘图
    
    '''
    def __init__(self):
        pass
    
    @staticmethod
    def plotNumericBox(df, figsize=(7,8), xlabel='', ylabel='', title='', path='', 
                      fontsize=20, testmethod='Mann-Whitney', testitem='Near'):
        '''
        以数据表格列为横坐标，绘制箱线图

        Parameters
        ----------
        df : DataFrame
            数据表格
        figsize : tuple(a,b)
            画布大小
        xlabel : str
            x轴标签
        ylabel : str
            y轴标签
        path : str
            保存路径
        fontsize : int
            刻度轴及标签字符大小
        testmethod : str
            假设检验方法:
                'Mann-Whitney','Mann-Whitney-gt','Mann-Whitney-ls',
                't-test_ind','t-test_welch','t-test_paired',
                'Wilcoxon','Wilcoxon-legacy',
                'Kruskal','Levene'.
        testitem : str
            假设检验项：'Near','All'

        Returns
        -------
        None.

        '''
        df = df.astype(float)
        plt.figure(figsize=figsize)
        box = sns.boxplot(data=df, palette="Reds", showmeans=True, notch=False)
        plt.rcParams['font.sans-serif']=['SimHei']
        plt.rcParams['axes.unicode_minus']=False
        plt.xlabel(xlabel, fontsize=fontsize)
        plt.ylabel(ylabel, fontsize=fontsize)
        plt.xticks(fontsize=fontsize)
        plt.yticks(fontsize=fontsize)
        plt.title(title, size=20)
        index = list(df.columns)
        if testitem == 'All':
            pairs = list(itertools.combinations(index, 2))
        if testitem == 'Near':
            pairs = []
            for i in range(0, len(index)-1):
                pair = tuple(index[i:i+2])
                pairs.append(pair)
        annotator = Annotator(box, pairs, data=df)
        annotator.configure(test=testmethod, text_format='star', verbose=False,
                            line_height=0.02,line_width=1,fontsize=fontsize)
        annotator.apply_and_annotate()
        if path == '':
            plt.show()
        else:
            plt.savefig(path, dpi=600)
        plt.close()
    
    @staticmethod
    def plotHeatmap(table, logarithm=False, cor_transpose=False, 
                    vmax=None, vmin=None, path=''):
        '''
        聚类热图和相关性聚类热图

        Parameters
        ----------
        table : DataFrame
            热图数据表格
        logarithm : bool
            数据是否取对数. The default is False.
        cor_transpose : bool
            相关性聚类热图是否转置. The default is False.
        vmax : int
            聚类热图图例最大值
        vmin : int
            聚类热图图例最小值
        path : str
            保存文件夹路径

        Returns
        -------
        None.

        '''
        if logarithm == True:
            table = table.apply(np.log1p)
        plt.figure()
        heatmap = seaborn.clustermap(table, pivot_kws=None, 
                                     method='average', metric='euclidean', 
                                     z_score=None, standard_scale=None, 
                                     figsize=(10, 10), cbar_kws=None, 
                                     row_cluster=True, col_cluster=True, 
                                     row_linkage=None, col_linkage=None, 
                                     row_colors=None, col_colors=None, 
                                     mask=None, dendrogram_ratio=0.2, 
                                     colors_ratio=0.03, tree_kws=None, 
                                     cbar_pos=(0.02, 0.8, 0.05, 0.18),
                                     vmax=vmax, vmin=vmin)
        if path != '':
            heatmap.savefig(path+'heatmap.png', dpi=300)
        if cor_transpose == True:
            table = table.T
        plt.figure()
        table_corr = table.corr(method='pearson')
        correlation_heatmap = seaborn.clustermap(table_corr)
        if path != '':
            correlation_heatmap.savefig(path+'correlation heatmap.png', 
                                        dpi=300)
    
    @staticmethod
    def plotMatrix(file_path='', start='', end='', labels=[], matrix='',
                   show_percentage=True):
        '''
        混淆矩阵的生成

        Parameters
        ----------
        file_path : str
            矩阵文件路径
        start : str
            文件中矩阵开始行
        end : str
            文件中矩阵结束行
        labels : list[str]
            矩阵标签
        matrix : str, e.g.'0.41,0.18,0.41; 0.25,0.63,0.12; 0.18,0,0.82'
            手动输入矩阵
        show_percentage : bool
            百分比显示. The default is True.

        Returns
        -------
        None.

        '''
        if file_path != '':
            data_list = []
            percentage_list = []
            with open(file_path,'r') as f:
                for i, line in enumerate(f.readlines()):
                    m = re.findall('\s*'+ start, line)
                    n = re.findall('\s*' + end, line)
                    if m:
                        x = i
                    if n:
                        y = i
            with open(file_path,'r') as f:  
                for j, lines in enumerate(f.readlines()):
                    if j in range(x, y+1):
                        data = re.findall('(?<= )\d+\.?\d*', lines)
                        if data:
                            data_list.append(data)
                matrix = np.array(data_list).astype(np.float64).T
            if show_percentage == True:
                for i in range(matrix.shape[0]):
                    label_sum = sum(matrix[i,:])
                    percentage = np.divide(matrix[i], label_sum)
                    percentage_list.append(percentage)
                matrix = np.array(percentage_list)    
        else:
            matrix = np.matrix('0.41,0.18,0.41; 0.25,0.63,0.12; 0.18,0,0.82')
        disp = ConfusionMatrixDisplay(confusion_matrix=matrix, 
                                      display_labels=labels)
        disp.plot(cmap='Blues')
        plt.show()
    
    @staticmethod
    def dataCorrelationFig(pathname, x, y, name, colordict, markerdict={}, 
                           xlabel='x', ylabel='y', tittle='tittle'):


        datasize = len(x)
        slist = []
        clist = []
        if len(name[0]) == 1:
            slist = 'o'
            for n in name:
                clist.append(colordict[n])
        else:
            for n in name[0]:
                slist.append(markerdict[n])
            if len(name) == 2:
                for n in name[1]:
                    clist.append(colordict[n])
            if len(name) > 2:
                nameback = name[1:]
                for i in range(datasize):
                    c = ''
                    for n in nameback:
                        c = c + '_'+n[i]
                    clist.append(colordict[c[1:]])
        print(slist, clist)
        unique_markers = set(slist)
        fig, ax = plt.subplots()

        ax.cla()
        for n in unique_markers:
            inds = [i for i, ele in enumerate(slist) if ele == n]
            xsub = [x[i] for i in inds]
            ysub = [y[i] for i in inds]
            csub = [clist[i] for i in inds]
            ax.scatter(xsub, ysub, marker=n, c=csub)
            print(xsub, ysub, csub)

        #para = np.poly1d(np.polyfit(x,y,1))
        #xp = np.linspace(min(x), max(x), 100)
        #plt.plot(xp, para(xp), '-k')
        # regression part
        x = np.asarray(x)
        y = np.asarray(y)
        slope, intercept, r_value, p_value, std_err = stats.linregress(x, y)
        line = slope*x+intercept
        ax.plot(x, line, 'k',
                label='y={:.2f}x+{:.2f}\t$r^2$ = {:.2f}'.format(slope, intercept, r_value))
        # end
        # ax.legend(fontsize=9)
        legend_elements = []
        if len(name[0]) > 1:
            for k, v in markerdict.items():
                legend_elements.append(Line2D([0], [0], marker=v, color='k', label=k,
                                              markerfacecolor='w', markersize=5))
        for k, v in colordict.items():
            legend_elements.append(Patch(facecolor=v, edgecolor='w',
                                         label=k))

        legend_elements.append(Line2D([0], [0], color='k', lw=1,
                                      label='y={:.2f}x+{:.2f}\n$r^2$ = {:.2f}'.
                                      format(slope, intercept, r_value)))
        box = ax.get_position()
        ax.set_position([box.x0, box.y0, box.width * 0.8, box.height])

        ax.legend(handles=legend_elements,
                  loc='center left', bbox_to_anchor=(1, 0.5))
        plt.title(tittle)
        plt.xlabel(xlabel)
        plt.ylabel(ylabel)
        plt.savefig(pathname, dpi=360)
        plt.show()
        
        
    @staticmethod
    def ANOVA_ONEWAY(df, p=0.95, maxedg=90, path='', verbose=True):
        def ANOVA_oneway(df, a=0.05):
            from scipy.stats import f
            '''
            进行单因素方差分析
            输入值：df - pd.DataFrame，第一列为水平，第二列为观测值；a - 显著性水平，默认为0.05
            返回类型：字典
            返回值：方差分析相关数据
            '''
            res = { 'SSA':0, 'SST':0 }
            mu = df[df.columns[1]].mean()
            da = df.groupby( df.columns[0] ).agg( {df.columns[1]:['mean','count']})
            da.columns = ['mean','count']
            res['df_A'] = len(list(da.index)) - 1        # 自由度
            # 组间误差平方和
            for row in da.index:
                res['SSA'] += (da.loc[row,'mean'] - mu )**2 * da.loc[row,'count']
            # 总误差平方和
            for e in df[df.columns[1]].values:
                res['SST'] += (e - mu )**2
            res['SSE'] = res['SST'] - res['SSA']         # 组内误差平方和
            res['df_E'] = len(df) - res['df_A'] - 1      # 残差自由度
            res['df_T'] = len(df) - 1                    # 总和自由度
            res['MSA'] = res['SSA'] / res['df_A']        # 组间均方
            res['MSE'] = res['SSE'] / res['df_E']        # 组内均方
            res['F'] = res['MSA'] / res['MSE']           # F值
            res['p_value'] = 1 - f(res['df_A'],res['df_E'] ).cdf( res['F'])  #p值
            res['a'] = a
            res['F_alpha'] = f(res['df_A'],res['df_E'] ).ppf( 1-a ) # 基于显著性水平a的F临界值
            return res
        
        def print_ANOVA_oneway(d, maxedg=90):
            '''
            打印单因素方差分析表
            输入值：d - dict字典，包含分析表所需要的数据; maxedg - 打印输出时装饰分隔符的最大长度
            '''
            title = '【单因素方差分析表】'
            print( title.center( maxedg ))
            print( '=' *  maxedg )
            print( '{:^12s}|{:^16s}|{:^6s}|{:^16s}|{:^12s}|{:^10s}|'.format('误差来源','平方和','自由度','均方和','F','p值'))
            print( '-' *  maxedg )
            print( '{:8s}|{:>18,.4f} |{:>8d} |{:>18,.4f} |{:>11.6f} |{:>10.3%} |'.format( '组间（因子影响）',d['SSA'],d['df_A'],d['MSA'],d['F'],d['p_value']))
            print( '{:10s}|{:>18,.4f} |{:>8d} |{:>18,.4f} |'.format( '组内（误差）',d['SSE'],d['df_E'],d['MSE']))
            print( '{:14s}|{:>18,.4f} |{:>8d} |'.format( '总和',d['SST'],d['df_T']))
            print( '-' *  maxedg )
            print('备注：显著性水平为 {:.2%} 时，F的临界值是 {:.6f}。'.format(d['a'],d['F_alpha']))
    
    
        res = ANOVA_oneway(df, a=1-p)
        if verbose == True:
            print_ANOVA_oneway(res, maxedg=maxedg)
        if path != '':
            key = df.columns[1]
            Analysis.saveData2Json(res, path + key + '.json')
        
        return res

    @staticmethod
    def ANOVA_ONEWAY2(df):
        from statsmodels.formula.api import ols
        from statsmodels.stats.anova import anova_lm
        
        formula = '{} ~ {}'.format(df.columns[1], df.columns[0])
        model = ols( formula, df ).fit()
        anovat = anova_lm(model)
        
        print(anovat)
        
    @staticmethod
    def saveData2Json(data, path=None):
        import json
        try:
            with open(path, 'w') as fr:
                json.dump(data, fr)
        except TypeError:
            try:
                for i in range(len(data)):
                    data[i][0]=data[i][0].tolist()
                with open(path, 'w') as fr:
                    json.dump(data, fr)
            except IndexError:
                data = data.tolist()
                with open(path, 'w') as fr:
                    json.dump(data, fr)
    
        
        
        
        
        